/*
    Project: OCMF (Operant Chamber Management Framework)
    Version: 1.0
    Author: Copyright (C) 2010, Samuel Delaney & Andrew Harvey
    Licence:
        This file is part of OCMF.

            OCMF is free software: you can redistribute it and/or modify
            it under the terms of the GNU General Public License as published by
            the Free Software Foundation, either version 3 of the License, or
            (at your option) any later version.

            OCMF is distributed in the hope that it will be useful,
            but WITHOUT ANY WARRANTY; without even the implied warranty of
            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            GNU General Public License for more details.

            You should have received a copy of the GNU General Public License
            along with OCMF.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ParallelPort.h"

ParallelPort::ParallelPort()
{
    //set default refreshRate
    refreshRate = 100;

    //set default port addresses
    inputPortAddress = 0x379;
    outputPortAddress = 0x378;

    //clear all outputs
    newOutput = 0b00000000;
    for(int i = 0; i < 8; i++)
        outputBits[i] = inputBits[i] = 0;

    //initialize function pointers to external functions
    GetApiHooks();

    portMutex.lock();

    start(NormalPriority);

    initWait.wait(&portMutex);

    portMutex.unlock();
}

ParallelPort::ParallelPort(short inputPortAddress, short outputPortAddress)
{
    //set default refreshRate
    refreshRate = 100;

    //set default port addresses
    ParallelPort::inputPortAddress = inputPortAddress;
    ParallelPort::outputPortAddress = outputPortAddress;

    //clear all outputs
    newOutput = 0b00000000;
    for(int i = 0; i < 8; i++)
        outputBits[i] = 0;

    //initialize function pointers to external functions
    GetApiHooks();

    //
    portMutex.lock();

    start(NormalPriority);

    initWait.wait(&portMutex);

    portMutex.unlock();
}

void ParallelPort::GetApiHooks()
{
    //Windows specific code
#ifdef Q_WS_WIN
    //import inpout32 library at runtime
    inpout32Lib.setFileName("inpout32.dll");

    //assign function pointers to functions contained in library
    if(inpout32Lib.resolve("Out32") != 0)
        Out32 = (Out32Prototype)inpout32Lib.resolve("Out32");
    else
    {
        Logger::AddEntry("Failed to resolve Out32 in inpout32.dll");
        exit(EXIT_FAILURE);
    }

    if(inpout32Lib.resolve("Inp32") != 0)
        Inp32 = (Inp32Prototype)inpout32Lib.resolve("Inp32");
    else
    {
        Logger::AddEntry("Failed to resolve Inp32 in inpout32.dll");
        exit(EXIT_FAILURE);
    }
#endif

    //Linux specific code
#ifdef Q_WS_X11
    //assign function pointers to functions contained in included header file
    Out32 = &outb;
    Inp32 = &inb;

    //set the access permissions in linux
    if (ioperm(port,3,1) != 0)
    {
        Logger::AddEntry("Could not obtain ports");
        exit(EXIT_FAILURE);
    }
#endif
}

void ParallelPort::TogglePort(short bit, unsigned int interval)
{
    //set bit, wait for duration, clear bit
    SetBit(bit);

    msleep(interval);

    ClearBit(bit);

    msleep(interval);
}

void ParallelPort::ClearBit(short bit)
{
    //set bit in output array
    outputBits[bit] = 0;

    //convert output to single variable
    newOutput = (1 * outputBits[0]) +
                (2 * outputBits[1]) +
                (4 * outputBits[2]) +
                (8 * outputBits[3]) +
                (16 * outputBits[4]) +
                (32 * outputBits[5]) +
                (64 * outputBits[6]) +
                (128 * outputBits[7]);

    Out32(outputPortAddress, newOutput);
    //    //use the mutex locker to lock/unlock mutex
//    QMutexLocker mutexLocker(&portMutex);
//
//    //set bit in output array
//    outputBits[bit] = 0;
//
//    qDebug("ParallelPort: Cleared Bit");
}

void ParallelPort::SetBit(short bit)
{
    //set bit in output array
    outputBits[bit] = 1;

    //convert output to single variable
    newOutput = (1 * outputBits[0]) +
                (2 * outputBits[1]) +
                (4 * outputBits[2]) +
                (8 * outputBits[3]) +
                (16 * outputBits[4]) +
                (32 * outputBits[5]) +
                (64 * outputBits[6]) +
                (128 * outputBits[7]);

    Out32(outputPortAddress, newOutput);

//    //use the mutex locker to lock/unlock mutex
//    QMutexLocker mutexLocker(&portMutex);
//
//    //set bit in output array
//    outputBits[bit] = 1;
//
//    qDebug("ParallelPort: Set Bit");
}

void ParallelPort::WaitForInput(short bit)
{
    //stop the port from being refreshed
    portMutex.lock();

    //keep checking the bit until its set
    while(inputBits[bit] == 0)
    {
        //allow the port to be refreshed
        portMutex.unlock();

        msleep(refreshRate);

        //stop the port from being refreshed
        portMutex.lock();
    }
}

void ParallelPort::run()
{
    //signal that the seperate thread has started
    initWait.wakeAll();

    //update port
    forever
    {
        //while the port is being accessed, block modification of buffer variables
        portMutex.lock();

        //retreive input
        newInput = Inp32(inputPortAddress);

        //break the input byte down
        if(newInput != oldInput)
        {
            this->inputBits[0] = ((newInput & 1) == 1);
            this->inputBits[1] = ((newInput & 2) == 2);
            this->inputBits[2] = ((newInput & 4) == 4);
            this->inputBits[3] = ((newInput & 8) == 8);
            this->inputBits[4] = ((newInput & 16) == 16);
            this->inputBits[5] = ((newInput & 32) == 32);
            this->inputBits[6] = ((newInput & 64) == 64);
            this->inputBits[7] = ((newInput & 128) == 128);

            oldInput = newInput;
        }

//        //convert output to single variable
//        newOutput = (1 * outputBits[0]) +
//                    (2 * outputBits[1]) +
//                    (4 * outputBits[2]) +
//                    (8 * outputBits[3]) +
//                    (16 * outputBits[4]) +
//                    (32 * outputBits[5]) +
//                    (64 * outputBits[6]) +
//                    (128 * outputBits[7]);
//
//        if(newOutput != oldOutput)
//        {
//            //write output byte to port
//            Out32(outputPortAddress, newOutput);
//
//            oldOutput = newOutput;
//        }

        //allow modification of buffer variables
        portMutex.unlock();

        //put thread to sleep for a bit
        QThread::msleep(refreshRate);
    }
}
